/**
 * @file boards\gd32f103cb\bsp\bsp_mobus_com.c
 *
 * Copyright (C) 2023
 *
 * bsp_mobus_com.c is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author HinsShum hinsshum@qq.com
 *
 * @encoding utf-8
 */

/*---------- includes ----------*/
#include "gd32f30x_libopt.h"
#include "options.h"
#include "serial.h"


/*---------- macro ----------*/
/*---------- type define ----------*/
/*---------- variable prototype ----------*/
/*---------- function prototype ----------*/
static bool bsp_init(void);
static void bsp_deinit(void);
static uint16_t bsp_write(uint8_t* pbuf, uint16_t length);
static void bsp_dir_choose(serial_direction_en dir);

/*---------- variable ----------*/
static serial_describe_t com = { .baudrate = 19200,
                                .comport = 2,
                                .ops.init = bsp_init,
                                .ops.deinit = bsp_deinit,
                                .ops.dir_change = bsp_dir_choose,
                                .ops.write = bsp_write,
                                .ops.byte_sent_ok = NULL,
                                .ops.irq_handler = NULL };
DEVICE_DEFINED(mobuscom, serial, &com);

/*---------- function ----------*/
static void bsp_io_init(void) {
  /* UART0 GPIO:
   *  TX: PA9
   *  RX: PA10
   *  TXEN: PA8
   */
    rcu_periph_clock_enable(RCU_GPIOA);
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
    gpio_bit_reset(GPIOA, GPIO_PIN_8);
}

static void bsp_io_deinit(void) {
    gpio_init(GPIOA, GPIO_MODE_OUT_OD, GPIO_OSPEED_2MHZ,
        GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10);
    gpio_bit_set(GPIOA, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10);
}

static void bsp_usart_init(void) {
    rcu_periph_clock_enable(RCU_USART0);

    usart_deinit(USART0);
    usart_baudrate_set(USART0, com.baudrate);
    usart_word_length_set(USART0, USART_WL_8BIT);
    usart_stop_bit_set(USART0, USART_STB_1BIT);
    usart_parity_config(USART0, USART_PM_NONE);
    usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
    usart_interrupt_enable(USART0, USART_INT_RBNE);
    usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE);
    usart_enable(USART0);
}

static void bsp_usart_deinit(void) {
    usart_disable(USART0);
    usart_interrupt_disable(USART0, USART_INT_RBNE);
    usart_deinit(USART0);
    rcu_periph_clock_disable(RCU_USART0);
}

static void bsp_nvic_init(void) {
    NVIC_ClearPendingIRQ(USART0_IRQn);
    NVIC_SetPriority(USART0_IRQn, NVIC_PRIORITY_MAKE(CONFIG_PRIORITY_COM0));
    NVIC_EnableIRQ(USART0_IRQn);
}

static void bsp_nvic_deinit(void) {
    NVIC_DisableIRQ(USART0_IRQn);
    NVIC_ClearPendingIRQ(USART0_IRQn);
}

static bool bsp_init(void) {
    bsp_io_init();
    bsp_usart_init();
    bsp_nvic_init();

    return true;
}

static void bsp_deinit(void) {
    bsp_nvic_deinit();
    bsp_usart_deinit();
    bsp_io_deinit();
}

static uint16_t bsp_write(uint8_t* pbuf, uint16_t length) {
    for (uint16_t i = 0; i < length; ++i) {
        usart_flag_clear(USART0, USART_FLAG_TC);
        usart_data_transmit(USART0, pbuf[i]);
        while (SET != usart_flag_get(USART0, USART_FLAG_TC))
            ;
    }

    return length;
}

static void bsp_dir_choose(serial_direction_en dir) {
    switch (dir) {
    case SERIAL_DIRECTION_TX:
        gpio_bit_set(GPIOA, GPIO_PIN_8);
        break;
    case SERIAL_DIRECTION_RX:
        gpio_bit_reset(GPIOA, GPIO_PIN_8);
        break;
    default:
        break;
    }
}
